% Copyright (C) 2006 Marc van Leeuwen
% This file is part of the Atlas of Reductive Lie Groups software (the Atlas)

% This program is made available under the terms stated in the GNU
% General Public License (GPL), see http://www.gnu.org/licences/licence.html

% The Atlas is free software; you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation; either version 2 of the License, or
% (at your option) any later version.

% The Atlas is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.

% You should have received a copy of the GNU General Public License
% along with the Atlas; if not, write to the Free Software
% Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA


@* Main program. This file defines a small main program to test the parser
under development. It is written in \Cpp, but is it mainly concerned with
interfacing to the parser that is generated by~\.{bison}, and which is
therefore written in~\Cee. Therefore we make very limited use of namespaces:
we only use the anonymous namespace for data local to this file; any calls
to \Cpp-code use explicit namespace resolution.

@d realex_version "0.5" // version numbering from 0.5, on 27 November 2010

@c

@< Declaration of interface to the parser @>@;
namespace { @< Local static data @>@; }@;
@< Definition of wrapper functions @>@;
@< Main program @>

@ Since the file \.{parser.y} declares \.{\%pure-parser} and \.{\%locations},
the prototype of the lexical analyser (wrapper) function |yylex| is the one
below. Curiously, the program~\.{bison} does not write this prototype to
\.{parser.tab.h}, but it does write the definitions of the types |YYSTYPE| and
|YYLTYPE| there; these require that \.{parsetree.h} be included first. We also
declare ``{\tt\%parse-param \char`\{} |int* verbosity, expr* parsed_expr@;|
{\tt\char`\}}'' in~\.{parser.y}, so that the parser itself, |yyparse|, takes
an integer pointer as parameter, which it uses to signal special requests from
the user (such as verbose output but also termination or output redirection),
and a pointer to an expression, in which it writes the result of parsing.
Since these functions form the interface to the parser written in~\Cee, we
must declare these definitions |extern "C"@;|.

@h "parsetree.h"
@h "parser.tab.h"

@< Declaration of interface to the parser @>=

extern "C"
{ int yylex (YYSTYPE *, YYLTYPE *);
@/int yyparse( expr* parsed_expr, int* verbosity );
}

@ Here is an array that declares the keywords that the lexical scanner is to
recognise, terminated by a null pointer. Currently the lexical analyser adds
the offset of the keyword in this list to |QUIT|, so the recognition depends
on the fact that |"quit"| is the first keyword, and that they are listed below
in the same order as in the \.{\%token} declarations in \.{parser.y}.

@< Local static data @>=

const char* keywords[] =
 {"quit"
 ,"set","let","in","begin","end"
 ,"if","then","else","elif","fi"
 ,"and","or","not"
 ,"while","do","od","next","for","from","downto"
 ,"true","false"
 ,"quiet","verbose"
 ,"whattype","showall","forget"
 ,NULL};

@ Here are the wrapper function for the lexical analyser and the error
reporting function, which are necessary because the parser cannot directly
call a class method. The prototypes are imposed, in particular the second and
third arguments to |yyerror| are those passed to |yyparse|, even though they
are not used in |yyerror|. In |yyerror| we close any open include files, as
continuing to execute their commands is undesirable.

@< Definition of wrapper functions @>=

extern "C"
int yylex(YYSTYPE *valp, YYLTYPE *locp)
{@; return atlas::interpreter::lex->get_token(valp,locp); }
@)
extern "C"
void yyerror (YYLTYPE* locp, expr* ,int* ,char const *s)
{ atlas::interpreter::main_input_buffer->show_range@|(std::cerr,
   locp->first_line, locp->first_column,
   locp->last_line,  locp->last_column);
  std::cerr << s << std::endl;
  atlas::interpreter::main_input_buffer->close_includes();
}


@ After a basic initialisation, our main program constructs unique instances
for various classes of the interpreter, and sets pointers to them so that
various compilation units can access them. Then in a loop it calls the parser
until it sets |verbosity<0|, which is done upon seeing the \.{quit} command.
We call the |reset| method of the lexical scanner before calling the parser,
which will discard any input that is left be a possible previous erroneous
input. This also already fetches a new line of input, or abandons the program
in case none can be obtained.

@h <iostream>
@h <fstream>
@h <readline/readline.h>
@h <readline/history.h>

@h "buffer.h"
@h "lexer.h"
@h "version.h"

@< Main program @>=

int main(int argc, char** argv)
{ using namespace std; using namespace atlas::interpreter;
@)
  @< Handle command line arguments @>

@/BufferedInput input_buffer("expr> "
                            ,use_readline ? readline : NULL
			    ,use_readline ? add_history : NULL);
  main_input_buffer= &input_buffer;
@/Hash_table hash; main_hash_table= &hash;
@/Lexical_analyser ana(input_buffer,hash,keywords,prim_names); lex=&ana;
@/Id_table main_table; @+ global_id_table=&main_table;
@/overload_table main_overload_table;
 @+ global_overload_table=&main_overload_table;
@)
  @< Initialise various parts of the program @>
@)
  cout << "This is 'realex', version " realex_version " (compiled on " @|
       << atlas::version::COMPILEDATE @| <<
").\nIt is the programmable interpreter interface to the library (version " @|
       << atlas::version::VERSION @| << ") of\n"
       << atlas::version::NAME << @| ". http://www.liegroups.org/\n";
  while (ana.reset()) // get a fresh line for lexical analyser, or quit
  { expr parse_tree;
    int old_verbosity=verbosity;
    ofstream redirect; // if opened, this will be closed at end of loop
    if (yyparse(&parse_tree,&verbosity)!=0)
      continue; // syntax error or non-expression
    if (verbosity!=0) // then some special action was requested
    { if (verbosity<0) break; // \.{quit} command
      if (verbosity==2 or verbosity==3)
        // indicates output redirection was requested
      { @< Open |redirect| to specified file, and if successful make
        |output_stream| point to it; otherwise |continue| @>
        verbosity=old_verbosity; // verbosity change was temporary
      }
      if (verbosity==1) //
        cout << "Expression before type analysis: " << parse_tree << endl;
    }
    @< Analyse types and then evaluate and print, or catch runtime or other
       errors @>
    output_stream= &cout; // reset output stream if it was changed
  }
  clear_history();
  // clean up (presumably disposes of the lines stored in history)
  cout << "Bye.\n";
}

@ Here are several calls necessary to get various parts of this program off to
a good start, starting with the history and readline libraries, and setting a
comment convention. Initialising the constants in the Atlas library is no
longer necessary, as it is done automatically before |main| is called. Our own
compilation units do require explicit calling of their initialisation
functions.

@h "built-in-types.h"
@h "constants.h"
@< Initialise various parts of the program @>=
  using_history();
  rl_completion_entry_function = id_completion_func; // set up input completion

@)ana.set_comment_delims('{','}');
@)initialise_evaluator(); initialise_builtin_types();

@ If a type error is detected by |analyse_types|, then it will have signalled
it and thrown a |runtime_error|; if that happens |type_OK| will remain |false|
and the runtime error is silently caught. If the result is an empty tuple, we
suppress printing if the uninteresting value.

@h <stdexcept>
@h "evaluator.h"

@< Analyse types and then evaluate and print... @>=
{ bool type_OK=false;
  try
  { expression_ptr e;
    type_ptr type=analyse_types(parse_tree,e);
    type_OK=true;
    if (verbosity>0)
      cout << "Type found: " << *type << endl @|
	   << "Converted expression: " << *e << endl;
    e->evaluate(expression_base::single_value);
    shared_value v=pop_value();
    static type_expr empty(type_list_ptr(NULL));
    if (*type!=empty) *output_stream << "Value: " << *v << endl;
    destroy_expr(parse_tree);
  }
  catch (runtime_error& err)
  { if (type_OK)
      cerr << "Runtime error:\n  " << err.what() << "\nEvaluation aborted.";
    else cerr << err.what();
    cerr << std::endl;
    reset_evaluator(); main_input_buffer->close_includes();
  }
  catch (logic_error& err)
  { cerr << "Internal error: " << err.what() << ", evaluation aborted.\n";
    reset_evaluator(); main_input_buffer->close_includes();
  }
  catch (exception& err)
  { cerr << err.what() << ", evaluation aborted.\n";
    reset_evaluator(); main_input_buffer->close_includes();
  }
}

@ For the moment the only command line argument accepted is \.{-nr}, which
indicates to not use the readline and history library in the input buffer.

@h <cstring>
@< Handle command line arguments @>=
bool use_readline = argc<2 or std::strcmp(argv[1],"-nr")!=0;


@ The |std::ofstream| object was already created earlier in the main loop,
but it will only be opened if we come here. If this fails then we report it
directly and |continue| to the next iteration of the main loop, which is more
practical at this point than throwing and catching an error.

@< Open |redirect| to specified file... @>=
{ redirect.open(ana.scanned_file_name() ,ios_base::out |
     (verbosity==2 ? ios_base::trunc : ios_base::@;app));
  if (redirect.is_open()) output_stream = &redirect;
  else
  {@; cerr << "Failed to open " << ana.scanned_file_name() << endl;
    continue;
  }
}

@* Index.

% Local IspellDict: british
